package edu.hawaii.myisern.model;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.bind.JAXBException;
import org.junit.Before;
import org.junit.Test;

import edu.hawaii.myisern.collaborations.jaxb.Collaboration;
import edu.hawaii.myisern.collaborations.jaxb.Years;
import edu.hawaii.myisern.organizations.jaxb.Organization;
import edu.hawaii.myisern.researchers.jaxb.Researcher;

import edu.hawaii.myisern.model.IsernModel.CollabLevelCompare;
/**
 * A test class showing the possible design of the class we'd like to
 * use to store our data.
 * Taken from first brown project, modified for purple.
 * 
 * @author Brian Jaress
 * @author Kevin English
 */
public class TestIsernModel {

  private static final String COLLABORATIONS_TEST_XML = "collaborations.test.xml";
  private static final String ORGANIZATIONS_TEST_XML = "organizations.test.xml";
  private static final String RESEARCHERS_TEST_XML = "researchers.test.xml";
  private static final String FAKE_NAME = "Fake Name";
  private static final String EXPECTED_EXCEPTION = "Didn't throw expected exception";


  // IDs from the sample data
  private static final Id COLLAB_ID = new Id("UM-UH-HPCS");
  private static final Id ORG_ID = new Id("University_of_Hawaii");
  private static final Id RESEARCHER_ID = new Id("Philip_Johnson");

  /**
   * A test model to be filled with correct data for testing queries.
   */
  public IsernModel model;

  private final String xmlDir;

  /**
   * Creates a test suite for IsernModel.
   */
  public TestIsernModel() {
    this.xmlDir = System.getProperty("user.dir") + "/xml/examples";
  }

  /**
   * Prepare a model from the sample data.
   * 
   * @throws IOException on bad data
   * @throws JAXBException if a parser cannot be created
   */
  @Before
  public void prepareModel() throws JAXBException, IOException {
    this.model = new IsernModel();
    model.setPath(xmlDir);
    this.model.setCollaborationsFile(COLLABORATIONS_TEST_XML);
    this.model.setResearchersFile( RESEARCHERS_TEST_XML);
    this.model.setOrganizationsFile(ORGANIZATIONS_TEST_XML);
    this.model.load();
  }

  /**
   * Checks the overall consistency and spot-checks the completeness of
   * the loaded data.
   */
  @Test
  public void testDataLoaded() {
    Set<Id> ids = model.getIds();
    assertTrue("A researcher ID", ids.contains(RESEARCHER_ID));
    assertTrue("An organization ID", ids.contains(ORG_ID));
    assertTrue("A collaboration ID", ids.contains(COLLAB_ID));
  }

  /**
   * Test queries that return researchers.
   */
  @Test
  public void testResearcherQuery() {
    // map for getting all researchers or one by ID
    Map<Id, Researcher> researchers = this.model.getResearchers();
    assertTrue("Researcher ID", researchers.containsKey(RESEARCHER_ID));
    Researcher r = researchers.get(RESEARCHER_ID);
    assertEquals("Researcher name", r.getName(), "Philip Johnson");
  }

  /**
   * Test queries that return organizations.
   */
  @Test
  public void testOrganizationQueries() {
    // map for getting all organizations or one by ID
    Map<Id, Organization> orgs = this.model.getOrganizations();
    assertTrue("Org ID", orgs.containsKey(ORG_ID));
    Organization o = orgs.get(ORG_ID);
    assertEquals("Org name", o.getName(), "University of Hawaii");

    // queries
    orgs = this.model.getOrganizationsByCollaborationLevel(
        CollabLevelCompare.EQUAL, 1);
    assertEquals("A single collaboration between two orgs", 2, orgs
        .size());
    assertTrue("Organization involved in collaboration", orgs
        .containsKey(ORG_ID));
    orgs = this.model.getOrganizationsByCollaborationLevel(
        CollabLevelCompare.GREATER, 1);
    assertTrue("No orgs with more than one collaboration", orgs
        .isEmpty());
  }

  /**
   * Test queries that return collaborations.
   */
  @Test
  public void testCollaborations() {
    // map for getting all collaborations or one by ID
    Map<Id, Collaboration> collabs = this.model.getCollaborations();
    assertTrue("Collab ID", collabs.containsKey(COLLAB_ID));
    Collaboration c = collabs.get(COLLAB_ID);
    assertEquals("Collab name", c.getName(), COLLAB_ID.toString());

    // queries
//    collabs = this.model.getCollaborationsByOrganization(ORG_ID);
//    assertEquals("One collaboration for the organization", 1, collabs
//        .size());
//    collabs = this.model.getCollaborationsByOrganization(NON_ID);
//    assertTrue("No collaborations", collabs.isEmpty());
//
//    collabs = this.model.getCollaborationsByYear(2005);
//    assertEquals("One collaboration in 2005", 1, collabs.size());
//    collabs = this.model.getCollaborationsByYear(1776);
//    assertTrue("No collaborations in 1776", collabs.isEmpty());
//
//    collabs = this.model.getCollaborationsByResearcher(RESEARCHER_ID);
//    assertEquals("One collaboration in 2005", 1, collabs.size());
//    collabs = this.model.getCollaborationsByResearcher(NON_ID);
//    assertTrue("No collaborations", collabs.isEmpty());
  }

  /**
   * Test loading from a single file which is expected to fail.
   * 
   * @param filePath the file path, which should contain the type of
   * data in the file (e.g. "researchers.xml")
   * @throws JAXBException on parser error
   * @throws IOException if input or output fails
   */
  private void badFileLoad(String filePath) throws JAXBException,
      IOException {
    IsernModel badModel = new IsernModel();
    badModel.setPath(xmlDir);
    badModel.setResearchersFile(RESEARCHERS_TEST_XML);
    badModel.setOrganizationsFile(ORGANIZATIONS_TEST_XML);
    badModel.setCollaborationsFile(COLLABORATIONS_TEST_XML);
    if (filePath.contains("researchers")) {
      badModel.setResearchersFile(filePath);
    }
    else if (filePath.contains("organizations")) {
      badModel.setOrganizationsFile(filePath);
    }
    else if (filePath.contains("collaborations")) {
      badModel.setCollaborationsFile(filePath);
    }
    badModel.load();
  }

  /**
   * Check that an exception is thrown on loading blank researcher names
   * 
   * @throws IOException on bad input
   * @throws JAXBException if a parser cannot be created
   */
  @Test(expected = IOException.class)
  public void testBlankNameResearchers() throws JAXBException,
      IOException {
    badFileLoad("researchers.blank-names.bad.xml");
    fail(EXPECTED_EXCEPTION);
  }

  /**
   * Check that an exception is thrown on loading blank organization
   * names
   * 
   * @throws IOException on bad input
   * @throws JAXBException if a parser cannot be created
   */
  @Test(expected = IOException.class)
  public void testBlankNameOrganizations() throws JAXBException,
      IOException {
    badFileLoad("organizations.blank-names.bad.xml");
    fail(EXPECTED_EXCEPTION);
  }

  /**
   * Check that an exception is thrown on loading blank collaboration
   * names
   * 
   * @throws IOException on bad input
   * @throws JAXBException if a parser cannot be created
   */
  @Test(expected = IOException.class)
  public void testBlankNameCollaborations() throws JAXBException,
      IOException {
    badFileLoad("collaborations.blank-names.bad.xml");
    fail(EXPECTED_EXCEPTION);
  }

  /**
   * Check that an exception is thrown on loading duplicate researcher
   * names
   * 
   * @throws IOException on bad input
   * @throws JAXBException if a parser cannot be created
   */
  @Test(expected = IOException.class)
  public void testDuplicateResearcherName() throws JAXBException,
      IOException {
    badFileLoad("researchers.duplicate-names.bad.xml");
    fail(EXPECTED_EXCEPTION);
  }

  /**
   * Check that an exception is thrown on loading duplicate organization
   * names
   * 
   * @throws IOException on bad input
   * @throws JAXBException if a parser cannot be created
   */
  @Test(expected = IOException.class)
  public void testDuplicateOrganizationName() throws JAXBException,
      IOException {
    badFileLoad("organizations.duplicate-names.bad.xml");
    fail(EXPECTED_EXCEPTION);
  }

  /**
   * Check that an exception is thrown on loading duplicate
   * collaboration names
   * 
   * @throws IOException on bad input
   * @throws JAXBException if a parser cannot be created
   */
  @Test(expected = IOException.class)
  public void testDuplicateCollaborationName() throws JAXBException,
      IOException {
    badFileLoad("collaborations.duplicate-names.bad.xml");
    fail(EXPECTED_EXCEPTION);
  }

  /**
   * Check that an exception is thrown on loading years that are too far
   * in the future.
   * 
   * @throws IOException on bad input
   * @throws JAXBException if a parser cannot be created
   */
  @Test(expected = IOException.class)
  public void testYearTooBig() throws JAXBException, IOException {
    badFileLoad("collaborations.big-year.bad.xml");
    fail(EXPECTED_EXCEPTION);
  }

  /**
   * Check that an exception is thrown on loading years that are too far
   * in the past.
   * 
   * @throws IOException on bad input
   * @throws JAXBException if a parser cannot be created
   */
  @Test(expected = IOException.class)
  public void testYearTooSmall() throws JAXBException, IOException {
    badFileLoad("collaborations.small-year.bad.xml");
    fail(EXPECTED_EXCEPTION);
  }

  /**
   * Test adding a researcher.
   * 
   * @throws IOException on duplicate ID
   */
  @Test
  public void testAddResearcher() throws IOException {
    Researcher r = new Researcher();
    r.setName(FAKE_NAME);
    this.model.addResearcher(r);
    assertTrue("Inserted Researcher", this.model.getResearchers()
        .containsValue(r));
  }

  /**
   * Test adding an organization
   * 
   * @throws IOException on duplicate ID
   */
  @Test
  public void testAddOrganization() throws IOException {
    Organization o = new Organization();
    o.setName(FAKE_NAME);
    this.model.addOrganization(o);
    assertTrue("Inserted Organization", this.model.getOrganizations()
        .containsValue(o));
  }

  /**
   * Test adding a collaboration
   * 
   * @throws IOException on duplicate ID
   */
  @Test
  public void testAddCollaboration() throws IOException {
    Collaboration c = new Collaboration();
    c.setName(FAKE_NAME);
    Years y = new Years();
    y.getYear().add(BigInteger.valueOf(2007));
    c.setYears(new Years());
    this.model.addCollaboration(c);
    assertTrue("Inserted Collaboration", this.model.getCollaborations()
        .containsValue(c));
  }

  /**
   * Test saving data to files and reloading.
   * 
   * @throws IOException on save error
   * @throws JAXBException if a parser cannot be created
   */
  @Test
  public void testSave() throws IOException, JAXBException {
    this.model.setPath(this.xmlDir);
    this.model.setResearchersFile("researchers.save.xml");
    this.model.setOrganizationsFile("organizations.save.xml");
    this.model.setCollaborationsFile("collaborations.save.xml");
    this.model.save();

    IsernModel reloaded = new IsernModel();
    reloaded.setPath(this.xmlDir) ;
    reloaded.setCollaborationsFile("collaborations.save.xml");
    reloaded.setOrganizationsFile("organizations.save.xml");
    reloaded.setResearchersFile("researchers.save.xml");
    reloaded.load();

    assertEquals("Same contents on reload", model.getIds(), reloaded
        .getIds());

    this.model.getResearchers().get(RESEARCHER_ID).setBioStatement(
        RESEARCHER_ID.toString());
    this.model.getOrganizations().get(ORG_ID).setResearchDescription(
        ORG_ID.toString());
    this.model.getCollaborations().get(COLLAB_ID).setDescription(
        COLLAB_ID.toString());

    this.model.save();
    reloaded.load();

    assertEquals("Edited researcher", reloaded.getResearchers().get(
        RESEARCHER_ID).getBioStatement(), (RESEARCHER_ID.toString()));
    assertEquals("Editted org", reloaded.getOrganizations().get(ORG_ID)
        .getResearchDescription(), (ORG_ID.toString()));
    assertEquals("Edited collab", reloaded.getCollaborations().get(
        COLLAB_ID).getDescription(), (COLLAB_ID.toString()));
  }

  /**
   * Check that we can get a sorted list of collaborations.
   */
  @Test
  public void testGetCollaborationList() {
    List<Collaboration> collaborationList = this.model
        .getCollaborationList();
    for (int i = 0; i < collaborationList.size() - 1; i++) {
      int compare = collaborationList.get(i).getName().compareTo(
          collaborationList.get(i + 1).getName());
      assertTrue("Sorted Collaborations", compare < 1);
    }

    for (Collaboration c : collaborationList) {
      assertTrue("Accurate listed Collaborations", this.model
          .getCollaborations().containsValue(c));
    }
    assertEquals("Complete listed Collaborations", this.model
        .getCollaborations().size(), collaborationList.size());
  }

  /**
   * Check that we can get a sorted list of organizations.
   */
  @Test
  public void testGetOrganizationList() {
    List<Organization> organizationList = this.model
        .getOrganizationList();
    for (int i = 0; i < organizationList.size() - 1; i++) {
      int compare = organizationList.get(i).getName().compareTo(
          organizationList.get(i + 1).getName());
      assertTrue("Sorted Organizations", compare < 1);
    }

    for (Organization c : organizationList) {
      assertTrue("Accurate listed Organizations", this.model
          .getOrganizations().containsValue(c));
    }
    assertEquals("Complete listed Organizations", this.model
        .getOrganizations().size(), organizationList.size());
  }

  /**
   * Check that we can get a sorted list of researchers.
   */
  @Test
  public void testGetResearcherList() {
    List<Researcher> researcherList = this.model.getResearcherList();
    for (int i = 0; i < researcherList.size() - 1; i++) {
      int compare = researcherList.get(i).getName().compareTo(
          researcherList.get(i + 1).getName());
      assertTrue("Sorted Researchers", compare < 1);
    }

    for (Researcher c : researcherList) {
      assertTrue("Accurate listed Researchers", this.model
          .getResearchers().containsValue(c));
    }
    assertEquals("Complete listed Researchers", this.model
        .getResearchers().size(), researcherList.size());
  }

//  /**
//   * Test the reference completions.
//   * 
//   * @throws IOException on bad data
//   * @throws JAXBException on parser error
//   */
//  @Test
//  public void testCompletion() throws IOException, JAXBException {
//    this.model.setCollaborationsFile("collaborations.completion.xml");
//    this.model.setOrganizationsFile( "organizations.completion.xml");
//    this.model.setResearchersFile("researchers.completion.xml");
//
//    this.model.getCollaborations().get(COLLAB_ID)
//        .getCollaboratingOrganizations().getCollaboratingOrganization()
//        .add(FAKE_NAME + "org1");
//
//    this.model.getOrganizations().get(ORG_ID)
//        .getAffiliatedResearchers().getAffiliatedResearcher().add(
//            FAKE_NAME + "researcher");
//
//    this.model.getResearchers().get(RESEARCHER_ID).setOrganization(
//        FAKE_NAME + "org2");
//
//    this.model.save();
//    this.model.load();
////
////    assertTrue("Completed org", this.model.getOrganizations()
////        .containsKey(new Id(FAKE_NAME + "org1")));
////    assertFalse("Org setting overrides researcher", this.model
////        .getOrganizations().containsKey(new Id(FAKE_NAME + "org2")));
////    assertEquals("Completed researcher", this.model.getOrganizations()
////        .get(ORG_ID).getName(), this.model.getResearchers().get(
////        new Id(FAKE_NAME + "researcher")).getOrganization());
//  }
}
